package regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author wenyuan
 * @version 1.0
 * @date 2023/7/24
 * @since 8
 */
public class RegexAround {

    public static void main(String[] args) {
        // 1. 初始的 Pattern Matcher PatternSyntaxException
        // usePrimaryily();
        // 2. 简化版本的方法也在 String 里有
        // useStringReg();
        // 3. 更广泛、更多的特性
        // useAdvancedly();
        // 4. 其他正则语法
        useOtherRegex();
    }

    /**
     * shorthand
     * <p>
     * 1. [^蒙多] -> 除了蒙多这俩个词外的所有词语  [a-e1-8] -> 匹配 a-e之间的所有字符，1-8之间的所有  <br/>
     * 2. \d 数字 \D 非数字 \s 任何空格 \S 任何非空格字符 \w 任何a-zA-Z_0-9 \W ^\w的缩写 \b 词语边界 \B 非词语边界   <br/>
     * 3. x? x最多出现一次 X* X出现任意次 X+ X出现大于等于一次 X{n} X应该出现n次 X{n,} X出现大于等于n次 X{n,m} X出现大于等于n次小于等于m次  <br/>
     * 4. () 我称之为分组，就是将未来被正则捕捉的内容进行分组，((a)(bc)) 有三个组，这个正则就可以使用 \\1 \\2 \\3 来重复调用这些组  <br/>
     * </p>
     * note
     * <p>
     * 1. 要重置匹配文本，应该使用 reset 方法，而不是 replaceAll，后者只是对先前文本匹配到的序列根据当前输入的文本进行替换 <br/>
     * 2. reset(...) 更换匹配内容，usePattern(...) 更换正则，这俩前后顺序没有干系  <br/>
     * 3. 一个现象，当使用了 matches() 匹配为真后，使用 find() 会查找不到，发现 find() 是匹配后续的内容，所以可以使用 reset() 重置一下 <br/>
     * </p>
     */
    private static void useOtherRegex() {
        // 1. 第二个Pattern
        Pattern pattern = Pattern.compile("[^蒙多]");
        Matcher matcher = pattern.matcher("蒙多说你是个大娘们！");
        findAllMatches(matcher);
        // 2. 第二个Pattern
        matcher.reset("戏命师痴迷 number 4");
        matcher.usePattern(Pattern.compile("[a-e1-8]"));
        findAllMatches(matcher);
        // 3. 第三个Pattern
        matcher.reset("1 2 3 4,yes sir!");
        matcher.usePattern(Pattern.compile("[\\d\\s\\S\\w]"));
        findAllMatches(matcher);
        // 4. 第四个Pattern
        matcher.usePattern(Pattern.compile("a?b*c+d{3,5}"));
        matcher.reset("aabcdddd");
        System.out.println(matcher.matches());  // aabcdddd false abcdddd true
        findAllMatches(matcher);
        // 5. 分组的使用
        matcher.usePattern(Pattern.compile("(我)(爱)(你)\\3\\2\\1"));
        matcher.reset("我爱你你爱我");
        System.out.println(matcher.matches());
        matcher.reset();
        findAllMatches(matcher);
    }

    /**
     * 找到所有符合正则的
     */
    private static void findAllMatches(Matcher matcher) {
        while (matcher.find()) {
            System.out.print(matcher.group());
        }
        System.out.println();
        System.out.println("------------");
    }

    /**
     * shorthand
     * [...] -> 匹配中括号内的任意字符 <br/>
     * note: 1. lookingAt 跟 matches 方法相同点都是查找是否正则匹配输入字符串，但不同的是 matches 要求正则匹配整个字符串，而 lookingAt 可以部分匹配<br/>
     * 2. 在使用 group() 方法前，要么使用 matches() or lookingAt();<br/>
     * 3. groupCount() 不包括第0组，也就是没有使用元组的整个纯模式<br/>
     * 4. Matcher并没有内置全局匹配所有符合的字符（像前端的/g），需要自己通过循环的方式实现，
     * 通过 循环 find()<br/>
     * 实际上 find() 代码实现部分几乎跟 lookingAt 是一样，但关键的是他们 使用的 Node 不一样<br/>
     * 两个维持的都是一颗正则树，find 的是 root树，match() 的是 matchRoot树<br/>
     * root树可以从任何地方开始匹配，而matchRoot树每次只能从开始匹配。<br/>
     * 所以 lookingAt 和 matches 都是要求输入字符序列必须连贯符合正则<br/>
     * 只是 lookingAt 它不要求必须匹配到最后，只有前面有跟正则匹配的就行，而 matches 必须全部匹配<br/>
     */
    private static void useAdvancedly() {
        Pattern pattern = Pattern.compile("[面如霜下雪]");
        String str = "面如霜下雪，吻如雪上霜。何以与君识，无言泪千行。";
        Matcher matcher = pattern.matcher(str);
        System.out.println("lookingAt 正则匹配部分，存在即可  " + matcher.lookingAt());
        System.out.println("这次匹配捕捉的组  " + matcher.group());
        System.out.println("上一个匹配字符的下一位： " + matcher.end());
        System.out.println("捕捉到的元组数量 " + matcher.groupCount());
        System.out.println("------------");
        System.out.println("使用find 进行查找下一位，但这并不是匹配，只是去查并放到group里  "
                + matcher.find());
        System.out.println("捕捉到的  " + matcher.group());
        System.out.println("捕捉到的元组数量 " + matcher.groupCount());
        System.out.println("------------");
        matcher.region(4, str.length());
        System.out.println("指定范围后，进行匹配  " + matcher.lookingAt());
        System.out.println("匹配捕捉到的组  " + matcher.group());
        System.out.println(matcher.end());
        System.out.println("------------");
        matcher.reset();        // 将之前的全部重置了
        StringBuilder sb = new StringBuilder();
        while (matcher.find()) {
            String group = matcher.group();
            sb.append(group).append(" ");
        }
        System.out.println("使用 find() 的方式 所有符合正则的话 " + sb);
        System.out.println("------------");
    }

    /**
     * Java 的正则不同于前端正则的实现方法，比如前端是这样的 /[aeiou]/g ，而 Java 是这样的 "^..." <br/>
     * 可以发现有一个关键区别：<br/>
     * 1. 前端的正则不是字符串，是使用特殊格式控制的<br/>
     * shorthand: ...$ 匹配行尾的字符<br/>
     */
    private static void useStringReg() {
        String str = "真正的大师永远都怀着一颗学徒的心";
        boolean matches1 = str.matches("学徒的心$");     // false
        boolean matches2 = str.matches("真正的大师永远都怀着一颗学徒的心$");       // true
        System.out.println(matches1);
        System.out.println(matches2);
    }

    /**
     * Pattern 没有公开构造方法，只提供了静态的 Compile 方法 <br/>
     * Matcher 同样也没有公开的构造方法，需使用 Pattern.matcher 方法来获得<br/>
     * Pattern 里的 flags 就是正则的模式，例如忽略大小写、点匹配等<br/>
     * 默认 flags 为 0(基本的)，而这些 flags 可以由 正则里的 ?u ?s 之类嵌入添加 flags，当然着不可能包含所有，也可以通过 compile 方法指定<br/>
     * shorthand: . -> 匹配任意单个字符  ^... -> 匹配行首的字符 <br/>
     * note: 1. matches 方法实际上要求的是，正则必须完全代表整个输入字符串 <br/>
     */
    private static void usePrimaryily() {
        // 1. 使用 Pattern.compile 方法使用正则
        Pattern pattern = Pattern.compile(".xx.");
        Matcher matcher = pattern.matcher("xxxx");// 参数类型 CharSequence 其实也是 String 的父接口
        System.out.println(matcher.matches());
        System.out.println("pattern.flags -> " + pattern.flags());
        // 2. 使用 Pattern.matches() 简化版本
        boolean matches1 = Pattern.matches("^you", "you are");       // false
        boolean matches2 = Pattern.matches("^you", "you");       // true
        System.out.println(matches1);
        System.out.println(matches2);
        String group = matcher.group();     // 如果没有抓取到匹配组则会抛出异常
        System.out.println(group);
    }

}

